/**
 * 
 */
package com.googlecode.cswish.struts;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;

import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.FrameConstant;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.validator.Validator;
import com.opensymphony.xwork2.validator.ValidatorConfig;
import com.opensymphony.xwork2.validator.ValidatorFactory;

/**
 * Auto-validation support:<br>
 * If the request path a matched model, framework uses the model property to validate the input parameter.<br>
 * <br>
 * The validation rules of "input parameter":<br>
 * <li> For the method with parameter, validate the request model parameter if exists.<br>
 * <li> For the method without parameter, validate the action instance.<br> 
 * <br>
 * @author Jerry.Feng Date: 2008-1-22
 */
//@Service
@Deprecated
public class AutoValidatorManager extends
		com.opensymphony.xwork2.validator.AnnotationActionValidatorManager {
	
	private static final Logger logger = Logger.getLogger(AutoValidatorManager.class);
	
	class ValidatorConfigCache {
		List<ValidatorConfig> configs;
		int pageElementsCascade;
	}
	
	/**
	 * Struts cache ValidatorConfig instead of the Validator, why? for the future extension? or just because of 'valueStack'?
	 * (validatorKey, List<Validator>>), see 'buildValidatorKey'
	 */
	private Map<String, ValidatorConfigCache> validatorCache = new HashMap<String, ValidatorConfigCache>();
	
	private ValidatorFactory validatorFactory;

	private ObjectFactory objectFactory;
	
	private BeanIntrospector beanIntrospector;

	private AutoValidator autoValidator;

	private Routing routing;
	

	@Inject
    public void setValidatorFactory(ValidatorFactory validatorFactory) {
        this.validatorFactory = validatorFactory;
        super.setValidatorFactory(validatorFactory);
        
        validatorFactory.registerValidator("collection", CollectionValidator.class.getName());
    }
	
	@Inject
	public void setObjectFactory(com.opensymphony.xwork2.ObjectFactory objectFactory) {
		this.objectFactory = ObjectFactory.getInstance();
		
		beanIntrospector = this.objectFactory.getBean(BeanIntrospector.class, false);
		autoValidator = this.objectFactory.getBean(AutoValidator.class, false);
		routing = this.objectFactory.getBean(Routing.class, false);
	}

	public void addValidator(String simpleClassName, String method, String validator) {
		// TODO: validator is added to validatorCache only when it's called firstly
		
	}
	
	public void reset(String key) {
		validatorCache.remove(key);
	}
	
	public void resetAll(String key) {
		String prefix = key;
		Iterator<String> iter = validatorCache.keySet().iterator();
		while (iter.hasNext()) {
			String cacheKey = iter.next();
			if (cacheKey.startsWith(prefix)) {
				iter.remove();
			}
		}
	}
	
	/**
	 * The method is called many times in order to generate the client JS, but the List<Validator>
	 * is calculated temporarily, I don't understand why xwork uses such a bad design!
	 * 
	 * @see Form.getValidators(String name)
	 */
	@Override
	public List<Validator> getValidators(Class clazz, String context, String method) {
		// try to read the cached value
		ValueStack stack = ActionContext.getContext().getValueStack();
		final String validatorKey = clazz.getName() + '-' 
				+ context + '-' + method + '-' + ActionContext.getContext().getLocale();
		List<Validator> fileValidators = (List<Validator>) stack.getContext().get(validatorKey);
		if (fileValidators != null) {
			return fileValidators;
		}
		
		// first call
		fileValidators = super.getValidators(clazz, context, method);

		ValidatorConfigCache cfgCache = validatorCache.get(validatorKey);
		
		List<ValidatorConfig> validatorCfgs;
		if (cfgCache == null) {
			validatorCfgs = initAutoValidations(validatorKey, new ArrayList<ValidatorConfig>(),
					clazz, context, method, stack);
		} else {
			validatorCfgs = cfgCache.configs;
		}
		
		if (validatorCfgs.size() > 0) {
			for (ValidatorConfig cfg : validatorCfgs) {
                Validator validator = validatorFactory.getValidator(cfg);
                validator.setValidatorType(cfg.getType());
                validator.setValueStack(stack);
                fileValidators.add(validator);
	        }
		}
		
		// cache the result to current ValueStack
		stack.getContext().put(validatorKey, fileValidators);
		return fileValidators;
	}
	
	private void prepareConfigs(List<ValidatorConfig> configs, String className, String methodName,
			String prefix, ValueStack stack) {
		ModelInfo modeInfo = beanIntrospector.getModelInfo(className);
		PageElements ps = (PageElements) stack.getContext().get(FrameConstant.STACK_PAGEELEMENTS);
		
		// FIXME: use ps.getElements(); Refactor the class later
		/*
		for (PropertyInfo info : modeInfo.getInterfaceProperties()) {
			if (info.getWriteMethod() == null) {
				continue;
			}
			
			String realClassName = info.getGenericType();
			
			if (beanIntrospector.isModel(realClassName) >= 0) {
				// load the default validation generated by the DB property
				Class<?> type = info.getTypeClass();
				boolean isArray  = type != null && Collection.class.isAssignableFrom(type);
				String thePrefix;
				if (isArray) {
					thePrefix = prefix + info.getName() + "[0]";
				} else {
					thePrefix = prefix + info.getName();
				}
				

				List<ValidatorConfig> list = autoValidator
						.getDefaultValidation(thePrefix + '.', realClassName, methodName, ps, isArray);
				configs.addAll(list);
				
				// FIXME:
				//prepareConfigs(configs, realClassName, methodName, thePrefix + '.', stack);
			}
		}*/
	}
	
	protected synchronized List<ValidatorConfig> initAutoValidations(
			String validatorKey, List<ValidatorConfig> parentFileValidators,
			Class clazz, String context, String method, 
			ValueStack stack) {
		Invoker invoker = routing.path2Invoker(context, true);
		List<ValidatorConfig> configs = new ArrayList<ValidatorConfig>();
		
		prepareConfigs(configs, clazz.getName(), invoker.methodName, "", stack);

		// add the key to check set
		Set<String> duplicateKeyCheck = new HashSet<String>();
		for (ValidatorConfig cfg : parentFileValidators) {
			duplicateKeyCheck.add(cfg.getType() + '@' + cfg.getParams().get("fieldName")
					+ '@' + cfg.getParams().get("extParam.validatorType"));
		}
		
		// combine two results
		List<ValidatorConfig> validatorCfgs = new ArrayList<ValidatorConfig>(configs.size());
		for (ValidatorConfig cfg : configs) {
            // skip the duplicate validation according to field name and validation type
            String key = cfg.getType() + '@' + cfg.getParams().get("fieldName")
            	+ '@' + cfg.getParams().get("extParam.validatorType");
            if (!duplicateKeyCheck.contains(key)) {
            	validatorCfgs.add(cfg);
            	duplicateKeyCheck.add(key);
            } else {
            	logger.debug("ignore the duplicate validatioin:" + key);
            }
        }
		
		ValidatorConfigCache configCache = new ValidatorConfigCache();
		configCache.configs = validatorCfgs;
		validatorCache.put(validatorKey, configCache);
		return validatorCfgs;
	}
	
    public List<Validator> getValidators(Class clazz, String context) {
    	int methodFlag = context.lastIndexOf('.');
    	String method = null;
    	if (methodFlag > 0) {
    		method = context.substring(methodFlag + 1);
    	}
        return getValidators(clazz, context, method);
    }
}
